#include "GraphicsItemFactory.h"

#include "LeIpc2581/Content.h"
#include "LeIpc2581/DictionaryColor.h"
#include "LeIpc2581/DictionaryLineDesc.h"
#include "LeIpc2581/DictionaryFillDesc.h"
#include "LeIpc2581/DictionaryStandard.h"
#include "LeIpc2581/DictionaryUser.h"
#include "LeIpc2581/enumtranslator.h"
#include "LeIpc2581/FeatureTypes.h"

#include "LeGraphicsView/LeGraphicsItem.h"

#include <QtMath>

#include <QDebug>
#include <QFont>
#include <QPainterPathStroker>
#include <QPen>

GraphicsItemFactory::GraphicsItemFactory(QObject *parent):
    QObject(parent)
{
    clearStats();
}

GraphicsItemFactory::~GraphicsItemFactory()
{
    clearDictionaries();
}

/*********************************************************************
 *
 * Dictionary management
 *
 ********************************************************************/

void GraphicsItemFactory::loadDictionaries(const Ipc2581b::Content *content)
{
    clearDictionaries();

    if (content->dictionaryStandardOptional.hasValue && content->dictionaryUserOptional.hasValue &&
            content->dictionaryStandardOptional.value->units != content->dictionaryUserOptional.value->units)
    {
        qDebug() << "Error: Standard and user dictionaries don't have the same unit";
        return;
    }
    if (content->dictionaryColorOptional.hasValue)
        loadColorDictionary(content->dictionaryColorOptional.value);
    if (content->dictionaryLineDescOptional.hasValue)
        loadPenDictionary(content->dictionaryLineDescOptional.value);
    if (content->dictionaryFillDescOptional.hasValue)
        loadBrushDictionary(content->dictionaryFillDescOptional.value);
    if (content->dictionaryStandardOptional.hasValue)
        loadStandardPrimitiveDictionary(content->dictionaryStandardOptional.value);
    if (content->dictionaryUserOptional.hasValue)
        loadUserPrimitiveDictionary(content->dictionaryUserOptional.value);
}

void GraphicsItemFactory::loadColorDictionary(const Ipc2581b::DictionaryColor *dict)
{
    for (auto entry: dict->entryColorList)
    {
        m_statCounter[StatisticKey::Color]++;
        m_colorDictionary.insert(entry->id, createColor(entry->color));
    }
}

QStringList GraphicsItemFactory::colorNames() const
{
    return m_colorDictionary.keys();
}

QColor GraphicsItemFactory::createColor(const QString &name) const
{
    m_statCounter[StatisticKey::ColorRef]++;

    if (!m_colorDictionary.contains(name))
    {
        qDebug() << "Color" << name << "not found!";
        return QColor();
    }

    return m_colorDictionary.value(name);
}

void GraphicsItemFactory::loadPenDictionary(const Ipc2581b::DictionaryLineDesc *dict)
{
    for (auto entry: dict->entryLineDescList)
    {
        m_statCounter[StatisticKey::Pen]++;
        m_penDictionary.insert(entry->id, createPen(entry->lineDesc));
    }
}

QStringList GraphicsItemFactory::penNames() const
{
    return m_penDictionary.keys();
}

QPen GraphicsItemFactory::createPen(const QString &name) const
{
    m_statCounter[StatisticKey::PenRef]++;

    if (!m_penDictionary.contains(name))
    {
        qDebug() << "Pen" << name << "not found!";
        return QPen(Qt::NoBrush, 0.0, Qt::NoPen);
    }

    return m_penDictionary.value(name);
}

void GraphicsItemFactory::loadBrushDictionary(const Ipc2581b::DictionaryFillDesc *dict)
{
    for (auto entry: dict->entryFillDescList)
    {
        m_statCounter[StatisticKey::Brush]++;
        m_brushDictionary.insert(entry->id, createBrush(entry->fillDesc));
    }
}

QStringList GraphicsItemFactory::brushNames() const
{
    return m_brushDictionary.keys();
}

QBrush GraphicsItemFactory::createBrush(const QString &name) const
{
    m_statCounter[StatisticKey::BrushRef]++;

    if (!m_brushDictionary.contains(name))
    {
        qDebug() << "Brush" << name << "not found!";
        return QBrush(Qt::NoBrush);
    }

    return m_brushDictionary.value(name);
}

void GraphicsItemFactory::loadStandardPrimitiveDictionary(const Ipc2581b::DictionaryStandard *dict)
{
    setIpcUnit(dict->units);

    for (auto entry: dict->entryStandardList)
    {
        m_statCounter[StatisticKey::StandardPrimitive]++;
        m_stdPrimitiveDictionary.insert(entry->id, createItem(entry->standardPrimitive, GftFill));
    }
}

QStringList GraphicsItemFactory::standardPrimitiveNames() const
{
    return m_stdPrimitiveDictionary.keys();
}

LeGraphicsItem *GraphicsItemFactory::createStandardPrimitive(const QString &name, LeGraphicsFeature featureType) const
{
    const LeGraphicsItem *original = m_stdPrimitiveDictionary.value(name);
    auto key = static_cast<StatisticKey>(original->data(ItemFactoryTypeKey).toInt());
    m_statCounter[key]++;
    m_statCounter[StatisticKey::StandardPrimitiveRef]++;
    auto cloned = original->clone();
    cloned->setFeatureType(featureType);
    return cloned;
}

void GraphicsItemFactory::loadUserPrimitiveDictionary(const Ipc2581b::DictionaryUser *dict)
{
    setIpcUnit(dict->units);

    for (auto entry: dict->entryUserList)
    {
        m_statCounter[StatisticKey::UserPrimitiveRef]++;
        m_userPrimitiveDictionary.insert(entry->id, createItem(entry->userPrimitive, GftFill));
    }
}

QStringList GraphicsItemFactory::userPrimitiveNames() const
{
    return m_userPrimitiveDictionary.keys();
}

LeGraphicsItem *GraphicsItemFactory::createUserPrimitive(const QString &name, LeGraphicsFeature featureType) const
{
    const LeGraphicsItem *original = m_userPrimitiveDictionary.value(name);
    auto key = static_cast<StatisticKey>(original->data(ItemFactoryTypeKey).toInt());
    m_statCounter[key]++;
    m_statCounter[StatisticKey::UserPrimitiveRef]++;
    auto cloned = original->clone();
    cloned->setFeatureType(featureType == GftNone ? original->featureType() : featureType);
    return cloned;
}


/*********************************************************************
 *
 * Color, Pen and Brush creators
 *
 ********************************************************************/

QColor GraphicsItemFactory::createColor(const Ipc2581b::Color *color) const
{
    m_statCounter[StatisticKey::Color]++;
    return QColor(color->r, color->g, color->b);
}

QColor GraphicsItemFactory::createColor(const Ipc2581b::ColorRef *colorRef) const
{
    return createColor(colorRef->id);
}

QColor GraphicsItemFactory::createColor(const Ipc2581b::ColorGroup *colorGroup) const
{
    switch (colorGroup->colorGroupType())
    {
        case Ipc2581b::ColorGroup::ColorGroupType::Color:
            return createColor(colorGroup->toColor());
        case Ipc2581b::ColorGroup::ColorGroupType::ColorRef:
            return createColor(colorGroup->toColorRef());
    }
    return QColor();
}

QPen GraphicsItemFactory::createPen(const Ipc2581b::LineDesc *lineDesc) const
{
    m_statCounter[StatisticKey::Pen]++;

    if (lineDesc->lineEnd != Ipc2581b::LineEnd::Round)
        qDebug() << "LineEnd: Only round end is supported!";

    if (lineDesc->linePropertyOptional.hasValue &&
            lineDesc->linePropertyOptional.value != Ipc2581b::LineProperty::Solid)
        qDebug() << "LineProperty: Only solid line is supported!";

    return QPen(Qt::black, mapFromIpc(lineDesc->lineWidth), Qt::SolidLine,
                Qt::RoundCap, Qt::RoundJoin);
}

QPen GraphicsItemFactory::createPen(const Ipc2581b::LineDescRef *lineDescRef) const
{
    return createPen(lineDescRef->id);
}

QPen GraphicsItemFactory::createPen(const Ipc2581b::LineDescGroup *lineDescGroup) const
{
    switch (lineDescGroup->lineDescGroupType())
    {
        case Ipc2581b::LineDescGroup::LineDescGroupType::LineDesc:
            return createPen(lineDescGroup->toLineDesc());
        case Ipc2581b::LineDescGroup::LineDescGroupType::LineDescRef:
            return createPen(lineDescGroup->toLineDescRef());
    }
    return QPen();
}

QBrush GraphicsItemFactory::createBrush(const Ipc2581b::FillDesc *fillDesc) const
{
    m_statCounter[StatisticKey::Brush]++;

    if (fillDesc->fillProperty != Ipc2581b::FillProperty::Fill)
        qDebug() << "FillProperty: Only Fill is supported!";

    QBrush brush(Qt::SolidPattern);
    if (fillDesc->colorOptional.hasValue)
        brush.setColor(createColor(fillDesc->colorOptional.value));
    return brush;
}

QBrush GraphicsItemFactory::createBrush(const Ipc2581b::FillDescRef *fillDescRef) const
{
    return createBrush(fillDescRef->id);
}

QBrush GraphicsItemFactory::createBrush(const Ipc2581b::FillDescGroup *fillDescGroup) const
{
    switch (fillDescGroup->fillDescGroupType())
    {
        case Ipc2581b::FillDescGroup::FillDescGroupType::FillDesc:
            return createBrush(fillDescGroup->toFillDesc());
        case Ipc2581b::FillDescGroup::FillDescGroupType::FillDescRef:
            return createBrush(fillDescGroup->toFillDescRef());
    }
    return QBrush();
}

/*********************************************************************
 *
 * Helper functions
 *
 ********************************************************************/

static const bool CombinedTransfrom = true;

// FIXME: the GraphicsScene unit influence memory usage due to path operations
// (set, stroking, ...)
void GraphicsItemFactory::setIpcUnit(Ipc2581b::Units unit)
{
    m_ipcScaleFactor = 1.0;
    return;

    m_ipcUnit = unit;
    switch (m_ipcUnit)
    {
        case Ipc2581b::Units::Inch:
            m_ipcScaleFactor = 2.54E3;
            break;
        case Ipc2581b::Units::Micron:
            m_ipcScaleFactor = 1E-3;
            break;
        case Ipc2581b::Units::Millimeter:
            m_ipcScaleFactor = 1E0;
            break;
        default:
            m_ipcScaleFactor = 1E0;
            break;
    }
}

template<class F>
LeGraphicsItem *bindFeature(LeGraphicsItem *item, const F *feature)
{
    item->setData(IpcDataKey, QVariant::fromValue<const F*>(feature));
    return item;
}

QPolygonF GraphicsItemFactory::createRegularPolygon(int sideCount, qreal radius, qreal startAngle)
{
    QPolygonF polygon(sideCount);
    qreal angle = startAngle;
    const qreal angleInc = 2*M_PI/sideCount;
    for (int i=0; i<sideCount; i++)
    {
        polygon[i] = QPointF(radius*qCos(angle),
                             radius*qSin(angle));
        angle += angleInc;
    }
    return polygon;
}

QPolygonF GraphicsItemFactory::createHexagon(qreal radius)
{
    return createRegularPolygon(6, radius, M_PI/6.0);
}

QPolygonF GraphicsItemFactory::createOctagon(qreal radius)
{
    return createRegularPolygon(8, radius, 0);
}

template <class T>
QPainterPath &GraphicsItemFactory::addPolyToPath(const T *poly, QPainterPath &path) const
{
    path.moveTo(mapFromIpc(poly->polyBegin->x, poly->polyBegin->y));
    for (auto step: poly->polyStepList)
    {
        if (step->isPolyStepSegment())
        {
            auto segment = step->toPolyStepSegment();
            path.lineTo(mapFromIpc(segment->x, segment->y));
        }
        if (step->isPolyStepCurve())
        {
            auto curve = step->toPolyStepCurve();
            bool ipcClockwise = true;
            if (curve->clockwiseOptional.hasValue)
                ipcClockwise = curve->clockwiseOptional.value;
            const QPointF here = path.currentPosition();
            const QPointF there = mapFromIpc(curve->x, curve->y);
            const QPointF center = mapFromIpc(curve->centerX, curve->centerY);
            const QLineF l1(center, here);
            const QLineF l2(center, there);
            const qreal radius = l1.length();
            const qreal start = l1.angle();
            qreal sweep = l1.angleTo(l2);
            if (!ipcClockwise)
                sweep -= 360.0;
            if (sweep > 0)
                sweep = std::fmod(sweep, 360.0);
            else
                sweep = -std::fmod(-sweep, 360.0);
            path.arcTo(center.x() - radius, center.y() - radius,
                       2*radius, 2*radius,
                       start, sweep);
        }
    }

    return path;
}

QPainterPath &GraphicsItemFactory::addPolygonToPath(const Ipc2581b::Polygon *polygon, QPainterPath &path) const
{
    addPolyToPath<Ipc2581b::Polygon>(polygon, path);
    path.closeSubpath();
    return path;
}

QPainterPath &GraphicsItemFactory::addPolylineToPath(const Ipc2581b::Polyline *polygon, QPainterPath &path) const
{
    addPolyToPath<Ipc2581b::Polyline>(polygon, path);
    return path;
}

QTransform GraphicsItemFactory::createTransform(Ipc2581b::Xform *xform) const
{
    m_statCounter[StatisticKey::Transform]++;

    QTransform transform;
    if (xform->xOffsetOptional.hasValue)
        transform.translate(mapFromIpc(xform->xOffsetOptional.value), 0);
    if (xform->yOffsetOptional.hasValue)
        transform.translate(0, mapFromIpc(xform->yOffsetOptional.value));
    // Seems like we need to mirror first, and then rotate, which is not what the spec says,
    // Is it due to Qt vs IPC coordinate system?
    if (xform->mirrorOptional.hasValue && xform->mirrorOptional.value)
        transform.scale(-1.0, 1.0);
    if (xform->rotationOptional.hasValue)
        transform.rotate(xform->rotationOptional.value);
    if (xform->scaleOptional.hasValue)
        transform.scale(xform->scaleOptional.value, xform->scaleOptional.value);
    return transform;
}


/*********************************************************************
 *
 * Standard primitive creators
 *
 ********************************************************************/

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Butterfly *primitive, LeGraphicsFeature featureType) const
{

    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::ButterflyRef));
    m_statCounter[StatisticKey::Butterfly]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));


    QPainterPath path;
    switch (primitive->shape)
    {
        case Ipc2581b::ButterflyShape::Round:
        {
            qreal diameter = mapFromIpc(primitive->diameterOptional.value);
            qreal radius = diameter/2.0;
            path.moveTo(0, 0);
            path.arcTo(-radius, -radius, diameter, diameter, 0, 90.0);
            path.lineTo(0, 0);
            path.arcTo(-radius, -radius, diameter, diameter, 180.0, 90.0);
            path.closeSubpath();
            break;
        }
        case Ipc2581b::ButterflyShape::Square:
        {
            qreal halfSide = mapFromIpc(primitive->sideOptional.value/2.0);

            path.moveTo(-halfSide, 0);
            path.lineTo(halfSide, 0);
            path.lineTo(halfSide, -halfSide);
            path.lineTo(0, -halfSide);
            path.lineTo(0, halfSide);
            path.lineTo(-halfSide, halfSide);
            path.lineTo(-halfSide, 0.0);
            break;
        }
    }
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Circle *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsCircleItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::CircleRef));
    m_statCounter[StatisticKey::Circle]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    item->setDiameter(mapFromIpc(primitive->diameter));

    return item;
}

// FIXME: polygons have transform, line desc and fill desc
LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Contour *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::ContourRef));
    m_statCounter[StatisticKey::Contour]++;

    QPainterPath path;
    path.setFillRule(Qt::OddEvenFill);
    addPolygonToPath(primitive->polygon, path);
    for (auto cutout: primitive->cutoutList)
        addPolygonToPath(cutout, path);
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Diamond *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::DiamondRef));
    m_statCounter[StatisticKey::Diamond]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    const qreal halfWidth = mapFromIpc(primitive->width/2.0);
    const qreal halfHeight = mapFromIpc(primitive->height/2.0);
    path.moveTo(0, -halfHeight);
    path.lineTo(halfWidth, 0);
    path.lineTo(0, halfHeight);
    path.lineTo(-halfWidth, 0);
    path.closeSubpath();
    item->setPath(path);

    return item;
}

// TODO: convert to addPolygonToPath
LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Donut *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::DonutRef));
    m_statCounter[StatisticKey::Donut]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath outPath;
    QPainterPath inPath;
    const qreal outDiameter = mapFromIpc(primitive->outerDiameter);
    const qreal outRadius = outDiameter/2.0;
    const qreal inDiameter = mapFromIpc(primitive->innerDiameter);
    const qreal inRadius = inDiameter/2.0;
    switch (primitive->shape)
    {
        case Ipc2581b::DonutShape::Round:
            outPath.addEllipse(-outRadius, -outRadius, outDiameter, outDiameter);
            inPath.addEllipse(-inRadius, -inRadius, inDiameter, inDiameter);
            break;
        case Ipc2581b::DonutShape::Square:
            outPath.addRect(-outRadius, -outRadius, outDiameter, outDiameter);
            inPath.addRect(-inRadius, -inRadius, inDiameter, inDiameter);
            break;
        case Ipc2581b::DonutShape::Hexagon:
            outPath.addPolygon(createHexagon(outRadius));
            inPath.addPolygon(createHexagon(inRadius));
            break;
        case Ipc2581b::DonutShape::Octagon:
            outPath.addPolygon(createOctagon(outRadius));
            inPath.addPolygon(createOctagon(inRadius));
            break;
    }

    QPainterPath path = outPath - inPath;
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Ellipse *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::EllipseRef));
    m_statCounter[StatisticKey::Ellipse]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    path.addEllipse(mapFromIpc(-primitive->width/2.0), mapFromIpc(-primitive->height/2.0),
                    mapFromIpc(primitive->width), mapFromIpc(primitive->height));
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Hexagon *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::HexagonRef));
    m_statCounter[StatisticKey::Hexagon]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    path.addPolygon(createHexagon(mapFromIpc(primitive->length/2.0)));
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Moire *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::MoireRef));
    m_statCounter[StatisticKey::Moire]++;

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    qreal radius = mapFromIpc(primitive->diameter/2.0);
    QPainterPath crossPath;
    const qreal x = radius * qCos(qDegreesToRadians(primitive->lineAngle));
    const qreal y = radius * qSin(qDegreesToRadians(primitive->lineAngle));
    crossPath.moveTo(-x ,-y);
    crossPath.lineTo(x, y);
    crossPath.moveTo(x, -y);
    crossPath.lineTo(-x, y);
    QPainterPathStroker stroker;
    stroker.setCapStyle(Qt::FlatCap);
    stroker.setWidth(primitive->lineWidth);
    crossPath = stroker.createStroke(crossPath);

    QPainterPath ringPath;
    for (int i=0; i<primitive->ringNumber; i++)
    {
        ringPath.addEllipse(-radius, -radius, 2.0*radius, 2.0*radius);
        radius -= mapFromIpc(primitive->ringGap);
    }
    stroker.setWidth(mapFromIpc(primitive->ringWidth));
    ringPath = stroker.createStroke(ringPath);

    QPainterPath path = crossPath + ringPath;
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Octagon *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::OctagonRef));
    m_statCounter[StatisticKey::Octagon]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    path.addPolygon(createOctagon(mapFromIpc(primitive->length/2.0)));
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Oval *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::OvalRef));
    m_statCounter[StatisticKey::Oval]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    const qreal width = mapFromIpc(primitive->width);
    const qreal height = mapFromIpc(primitive->height);
    const qreal halfWidth = width/2.0;
    const qreal halfHeight = height/2.0;
    path.moveTo(-halfWidth+halfHeight, -halfHeight);
    path.lineTo(halfWidth-halfHeight, -halfHeight);
    path.arcTo(halfWidth-height, -halfHeight, height, height, 90, -180);
    path.lineTo(-halfWidth+halfHeight, halfHeight);
    path.arcTo(-halfWidth, -halfHeight, height, height, -90, -180);
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::RectCenter *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsRectItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::RectCenterRef));
    m_statCounter[StatisticKey::RectCenter]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    item->setWidth(mapFromIpc(primitive->width));
    item->setHeight(mapFromIpc(primitive->height));

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::RectCham *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::RectChamRef));
    m_statCounter[StatisticKey::RectCham]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    bool ur = primitive->upperRightOptional.hasValue && primitive->upperRightOptional.value;
    bool ul = primitive->upperLeftOptional.hasValue && primitive->upperLeftOptional.value;
    bool ll = primitive->lowerLeftOptional.hasValue && primitive->lowerLeftOptional.value;
    bool lr = primitive->lowerRightOptional.hasValue && primitive->lowerRightOptional.value;
    const qreal halfWidth = mapFromIpc(primitive->width/2.0);
    const qreal halfHeight = mapFromIpc(primitive->height/2.0);
    const qreal chamfer = mapFromIpc(primitive->chamfer);
    if (lr)
    {
        path.moveTo(halfWidth, -halfHeight+chamfer);
        path.lineTo(halfWidth-chamfer, -halfHeight);
    }
    else
        path.moveTo(halfWidth, -halfHeight);
    if (ll)
    {
        path.lineTo(-halfWidth+chamfer, -halfHeight);
        path.lineTo(-halfWidth, -halfHeight+chamfer);
    }
    else
        path.lineTo(-halfWidth, -halfHeight);
    if (ul)
    {
        path.lineTo(-halfWidth, halfHeight-chamfer);
        path.lineTo(-halfWidth+chamfer, halfHeight);
    }
    else
        path.lineTo(-halfWidth, halfHeight);
    if (ur)
    {
        path.lineTo(halfWidth-chamfer, halfHeight);
        path.lineTo(halfWidth, halfHeight-chamfer);
    }
    else
        path.lineTo(halfWidth, halfHeight);
    path.closeSubpath();
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::RectCorner *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::RectCornerRef));
    m_statCounter[StatisticKey::RectCorner]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    QRectF rect;
    rect.setBottomLeft(mapFromIpc(primitive->lowerLeftX, primitive->lowerLeftY));
    rect.setTopRight(mapFromIpc(primitive->upperRightX, primitive->upperRightY));
    path.addRect(rect);
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::RectRound *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::RectRoundRef));
    m_statCounter[StatisticKey::RectRound]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;

    bool ur = primitive->upperRightOptional.hasValue && primitive->upperRightOptional.value;
    bool ul = primitive->upperLeftOptional.hasValue && primitive->upperLeftOptional.value;
    bool ll = primitive->lowerLeftOptional.hasValue && primitive->lowerLeftOptional.value;
    bool lr = primitive->lowerRightOptional.hasValue && primitive->lowerRightOptional.value;
    const qreal halfWidth = mapFromIpc(primitive->width/2.0);
    const qreal halfHeight = mapFromIpc(primitive->height/2.0);
    const qreal radius = mapFromIpc(primitive->radius);
    QRectF rect(-radius, -radius, 2*radius, 2*radius);
    if (lr)
    {
        path.moveTo(halfWidth, -halfHeight+radius);
        path.arcTo(rect.translated(halfWidth-radius, -halfHeight+radius), 0, 90);
    }
    else
        path.moveTo(halfWidth, -halfHeight);
    if (ll)
    {
        path.lineTo(-halfWidth+radius, -halfHeight);
        path.arcTo(rect.translated(-halfWidth+radius, -halfHeight+radius), 90, 90);
    }
    else
        path.lineTo(-halfWidth, -halfHeight);
    if (ul)
    {
        path.lineTo(-halfWidth, halfHeight-radius);
        path.arcTo(rect.translated(-halfWidth+radius, halfHeight-radius), 180, 90);
    }
    else
        path.lineTo(-halfWidth, halfHeight);
    if (ur)
    {
        path.lineTo(halfWidth-radius, halfHeight);
        path.arcTo(rect.translated(halfWidth-radius, halfHeight-radius), 270, 90);
    }
    else
        path.lineTo(halfWidth, halfHeight);
    path.closeSubpath();
    item->setPath(path);

    return item;
}

// TODO: convert to addPolygonToPath
LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Thermal *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::ThermalRef));
    m_statCounter[StatisticKey::Thermal]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath outPath;
    QPainterPath inPath;
    const qreal outDiameter = mapFromIpc(primitive->outerDiameter);
    const qreal outRadius = outDiameter/2.0;
    const qreal inDiameter = mapFromIpc(primitive->innerDiameter);
    const qreal inRadius = inDiameter/2.0;
    switch (primitive->shape)
    {
        case Ipc2581b::ThermalShape::Round:
            outPath.addEllipse(-outRadius, -outRadius, outDiameter, outDiameter);
            inPath.addEllipse(-inRadius, -inRadius, inDiameter, inDiameter);
            break;
        case Ipc2581b::ThermalShape::Square:
            outPath.addRect(-outRadius, -outRadius, outDiameter, outDiameter);
            inPath.addRect(-inRadius, -inRadius, inDiameter, inDiameter);
            break;
        case Ipc2581b::ThermalShape::Hexagon:
            outPath.addPolygon(createHexagon(outRadius));
            inPath.addPolygon(createHexagon(inRadius));
            break;
        case Ipc2581b::ThermalShape::Octagon:
            outPath.addPolygon(createOctagon(outRadius));
            inPath.addPolygon(createOctagon(inRadius));
            break;
    }

    QPainterPath crossPath;
    qreal gap = outDiameter - inDiameter;
    if (primitive->gapOptional.hasValue)
        gap = mapFromIpc(primitive->gapOptional.value);
    qreal radius = 2*outRadius;
    int count = 4;
    if (primitive->spokeCountOptional.hasValue)
        count = primitive->spokeCountOptional.value;
    const qreal angleInc = 360.0/count;
    qreal angle = primitive->spokeStartAngle;
    for (int i=0; i<count; i++)
    {
        const qreal x = radius * qCos(qDegreesToRadians(angle));
        const qreal y = radius * qSin(qDegreesToRadians(angle));
        crossPath.moveTo(0, 0);
        crossPath.lineTo(x, y);
        angle += angleInc;
    }
    QPainterPathStroker stroker;
    stroker.setCapStyle(Qt::FlatCap);
    stroker.setWidth(gap);
    crossPath = stroker.createStroke(crossPath);

    QPainterPath path = outPath - inPath - crossPath;
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Triangle *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::TriangleRef));
    m_statCounter[StatisticKey::Triangle]++;

//    if (primitive->fillDescGroupOptional.hasValue)
//        item->setFillStyle(createBrush(primitive->fillDescGroupOptional.value).style());

    if (primitive->xformOptional.hasValue)
        item->setTransform(createTransform(primitive->xformOptional.value));

    QPainterPath path;
    path.moveTo(-mapFromIpc(primitive->base/2.0, 0));
    path.lineTo(mapFromIpc(primitive->base/2.0, 0));
    path.lineTo(mapFromIpc(0, primitive->height/2.0));
    path.closeSubpath();
    item->setPath(path);

    return item;
}

LeGraphicsItem *GraphicsItemFactory::createStandardPrimitive(const Ipc2581b::StandardPrimitive *primitive, LeGraphicsFeature featureType) const
{
    m_statCounter[StatisticKey::StandardPrimitive]++;
    switch (primitive->standardPrimitiveType())
    {
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Butterfly:
            return createItem(primitive->toButterfly(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Circle:
            return createItem(primitive->toCircle(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Contour:
            return createItem(primitive->toContour(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Diamond:
            return createItem(primitive->toDiamond(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Donut:
            return createItem(primitive->toDonut(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Ellipse:
            return createItem(primitive->toEllipse(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Hexagon:
            return createItem(primitive->toHexagon(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Moire:
            return createItem(primitive->toMoire(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Octagon:
            return createItem(primitive->toOctagon(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Oval:
            return createItem(primitive->toOval(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::RectCenter:
            return createItem(primitive->toRectCenter(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::RectCham:
            return createItem(primitive->toRectCham(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::RectCorner:
            return createItem(primitive->toRectCorner(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::RectRound:
            return createItem(primitive->toRectRound(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Thermal:
            return createItem(primitive->toThermal(), featureType);
        case Ipc2581b::StandardPrimitive::StandardPrimitiveType::Triangle:
            return createItem(primitive->toTriangle(), featureType);
    }
    return nullptr;
}

LeGraphicsItem * GraphicsItemFactory::createStandardPrimitive(const Ipc2581b::StandardPrimitiveRef *ref, LeGraphicsFeature featureType) const
{
    m_statCounter[StatisticKey::StandardPrimitiveRef]++;
    return createStandardPrimitive(ref->id, featureType);
}

LeGraphicsItem *GraphicsItemFactory::createStandardPrimitive(const Ipc2581b::StandardShape *shape, LeGraphicsFeature featureType) const
{
    switch (shape->standardShapeType())
    {
        case Ipc2581b::StandardShape::StandardShapeType::StandardPrimitive:
            return createStandardPrimitive(shape->toStandardPrimitive(), featureType);
        case Ipc2581b::StandardShape::StandardShapeType::StandardPrimitiveRef:
            return createStandardPrimitive(shape->toStandardPrimitiveRef(), featureType);
    }
    return nullptr;
}


/*********************************************************************
 *
 * User primitive creators
 *
 ********************************************************************/


// FIXME:
LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Arc *primitive, LeGraphicsFeature featureType) const
{
    QPainterPath path;
    const QPointF here = mapFromIpc(primitive->startX, primitive->startY);
    const QPointF there = mapFromIpc(primitive->endX, primitive->endY);
    const QPointF center = mapFromIpc(primitive->centerX, primitive->centerY);
    bool ipcClockwise = primitive->clockwise;
    const QLineF l1(center, here);
    const QLineF l2(center, there);
    const qreal radius = l1.length();
    const qreal start = l1.angle();
    qreal sweep = l1.angleTo(l2);
    if (!ipcClockwise)
        sweep -= 360.0;

    path.arcTo(center.x() - radius, center.y() - radius,
               2*radius, 2*radius,
               start, sweep);

    auto pen = createPen(primitive->lineDescGroup);
    auto item = new LeGraphicsStrokedPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::ArcRef));
    m_statCounter[StatisticKey::Arc]++;
    item->setPath(path, pen.widthF());
    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Line *primitive, LeGraphicsFeature featureType) const
{
    QPainterPath path;
    path.moveTo(mapFromIpc(primitive->startX, primitive->startY));
    path.lineTo(mapFromIpc(primitive->endX, primitive->endY));

    auto pen = createPen(primitive->lineDescGroup);
    auto item = new LeGraphicsStrokedPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::LineRef));
    m_statCounter[StatisticKey::Line]++;
    item->setPath(path, pen.widthF());
    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Outline *primitive, LeGraphicsFeature featureType) const
{
    QPainterPath path;
    addPolygonToPath(primitive->polygon, path);

    QTransform transform;
    if (primitive->polygon->xformOptional.hasValue)
        transform = createTransform(primitive->polygon->xformOptional.value);

    auto pen = createPen(primitive->lineDescGroup);
    auto item = new LeGraphicsStrokedPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::OutlineRef));
    m_statCounter[StatisticKey::Outline]++;
    item->setPath(path, pen.widthF());
    item->setTransform(transform);
    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Polyline *primitive, LeGraphicsFeature featureType) const
{
    QPainterPath path;
    addPolylineToPath(primitive, path);

    auto pen = createPen(primitive->lineDescGroup);
    auto item = new LeGraphicsStrokedPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::PolylineRef));
    m_statCounter[StatisticKey::Polyline]++;
    item->setPath(path, pen.widthF());
    return item;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Simple *primitive, LeGraphicsFeature featureType) const
{
    m_statCounter[StatisticKey::Simple]++;
    switch (primitive->simpleType())
    {
        case Ipc2581b::Simple::SimpleType::Arc:
            return createItem(primitive->toArc(), featureType);
        case Ipc2581b::Simple::SimpleType::Line:
            return createItem(primitive->toLine(), featureType);
        case Ipc2581b::Simple::SimpleType::Outline:
            return createItem(primitive->toOutline(), featureType);
        case Ipc2581b::Simple::SimpleType::Polyline:
            return createItem(primitive->toPolyline(), featureType);
    }
    return nullptr;
}

// FIXME: Font family and size
LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Text *primitive, LeGraphicsFeature featureType) const
{
    Q_UNUSED(primitive)
    auto item = new LeGraphicsFilledPathItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::TextRef));
    m_statCounter[StatisticKey::Text]++;

    return item;
}

// FIXME: This is actually an aggregation of GraphicsFeature's, each having their own pen/brush...
// FIXME: Should this behave as a single item? QGraphicsItemGroup?
LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::UserSpecial *primitive, LeGraphicsFeature featureType) const
{
    auto item = new LeGraphicsCompositeItem(featureType);
    item->setData(ItemFactoryTypeKey, int(StatisticKey::UserSpecialRef));
    m_statCounter[StatisticKey::UserSpecial]++;

    for (auto feature: primitive->featureList)
    {
        auto childItem = createItem(feature, featureType);
        childItem->setParentItem(item);
    }
    return item;
}

LeGraphicsItem *GraphicsItemFactory::createUserPrimitive(const Ipc2581b::UserPrimitive *primitive, LeGraphicsFeature featureType) const
{
    m_statCounter[StatisticKey::UserPrimitive]++;
    switch (primitive->userPrimitiveType())
    {
        case Ipc2581b::UserPrimitive::UserPrimitiveType::Simple:
            return createItem(primitive->toSimple(), featureType == GftNone ? GftTrace : featureType);
        case Ipc2581b::UserPrimitive::UserPrimitiveType::Text:
            return createItem(primitive->toText(), featureType == GftNone ? GftText : featureType);
        case Ipc2581b::UserPrimitive::UserPrimitiveType::UserSpecial:
            return createItem(primitive->toUserSpecial(), featureType == GftNone ? GftFill : featureType); // FIXME
    }
    return nullptr;
}

LeGraphicsItem * GraphicsItemFactory::createUserPrimitive(const Ipc2581b::UserPrimitiveRef *ref, LeGraphicsFeature featureType) const
{
    m_statCounter[StatisticKey::UserPrimitiveRef]++;
    return createUserPrimitive(ref->id, featureType);
}

LeGraphicsItem *GraphicsItemFactory::createUserPrimitive(const Ipc2581b::UserShape *shape, LeGraphicsFeature featureType) const
{
    m_statCounter[StatisticKey::UserShape]++;
    switch (shape->userShapeType())
    {
        case Ipc2581b::UserShape::UserShapeType::UserPrimitive:
            return createUserPrimitive(shape->toUserPrimitive(), featureType);
        case Ipc2581b::UserShape::UserShapeType::UserPrimitiveRef:
            return createUserPrimitive(shape->toUserPrimitiveRef(), featureType);
    }
    return nullptr;
}

/*********************************************************************
 *
 * High level primitive creators
 *
 ********************************************************************/

QList<LeGraphicsItem *> GraphicsItemFactory::createItems(const Ipc2581b::Features *features) const
{
    m_statCounter[StatisticKey::Features]++;

    QList<LeGraphicsItem *> result;

    QTransform transform;
    if (features->xformOptional.hasValue)
        transform = createTransform(features->xformOptional.value);

    for (auto location: features->locationList)
    {
        auto item = createItem(features->feature, GftNone);
        item->setTransform(transform, CombinedTransfrom);
        item->setPos(mapFromIpc(location->x, location->y));
        result.append(bindFeature(item, features));
    }

    return result;
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Pad *pad) const
{
    QTransform transform;
    if (pad->xformOptional.hasValue)
        transform = createTransform(pad->xformOptional.value);

    QPointF location = mapFromIpc(pad->location->x,
                                  pad->location->y);

    // FIXME:
    auto item = createItem(pad->feature, GftPad);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::TextRef));
    m_statCounter[StatisticKey::Pad]++;
    item->setPos(location);
    item->setTransform(transform, CombinedTransfrom);

    return bindFeature(item, pad);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Pin *pin) const
{
    QTransform transform;
    if (pin->xformOptional.hasValue)
        transform = createTransform(pin->xformOptional.value);

    QPointF location;
    if (pin->locationOptional.hasValue)
        location = mapFromIpc(pin->locationOptional.value->x,
                              pin->locationOptional.value->y);

    auto item = createItem(pin->standardShape, GftPin);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::TextRef));
    m_statCounter[StatisticKey::Pin]++;
    item->setPos(location);
    item->setTransform(transform, CombinedTransfrom);

    return bindFeature(item, pin);
}

// FIXME
LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Fiducial *fiducial) const
{
    Q_UNUSED(fiducial)

    auto item = new LeGraphicsFilledPathItem(GftFiducial);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::FiducialRef));
    m_statCounter[StatisticKey::Fiducial]++;

    return bindFeature(item, fiducial);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Hole *hole) const
{
    auto item = new LeGraphicsCircleItem(GftHole);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::HoleRef));
    m_statCounter[StatisticKey::Hole]++;
    item->setPos(mapFromIpc(hole->x, hole->y));
    item->setDiameter(mapFromIpc(hole->diameter));

    return bindFeature(item, hole);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::SlotCavity *slotCavity) const
{
    auto item = new LeGraphicsCompositeItem(GftSlot);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::SlotRef));
    m_statCounter[StatisticKey::Slot]++;

    for (auto simple: slotCavity->simpleList)
    {
        auto childItem = createItem(simple, GftSlot);
        childItem->setParentItem(item);
    }

    return bindFeature(item, slotCavity);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Target *target) const
{
    QTransform transform;
    if (target->xformOptional.hasValue)
        transform = createTransform(target->xformOptional.value);

    QPointF location = mapFromIpc(target->location->x,
                                  target->location->y);

    auto item = createItem(target->standardShape, GftFill);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::TargetRef));
    m_statCounter[StatisticKey::Target]++;
    item->setPos(location);
    item->setTransform(transform, CombinedTransfrom);

    return bindFeature(item, target);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Marking *marking) const
{
    QTransform transform;
    if (marking->xformOptional.hasValue)
        transform = createTransform(marking->xformOptional.value);

    QPointF location;
    if (marking->locationOptional.hasValue)
        location = mapFromIpc(marking->locationOptional.value->x,
                              marking->locationOptional.value->y);

    auto item = createItem(marking->feature, GftFill);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::TextRef));
    m_statCounter[StatisticKey::Marking]++;
    item->setPos(location);
    item->setTransform(transform, CombinedTransfrom);

    return bindFeature(item, marking);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::PadstackHoleDef *hole) const
{
    auto item = new LeGraphicsCircleItem(GftHole);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::TextRef));
    //m_statCounter[StatisticKey::PadstackHoleDef]++;
    item->setPos(mapFromIpc(hole->x, hole->y));
    item->setDiameter(mapFromIpc(hole->diameter));

    return bindFeature(item, hole);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::PadstackPadDef *pad) const
{
    QTransform transform;
    if (pad->xformOptional.hasValue)
        transform = createTransform(pad->xformOptional.value);

    QPointF location = mapFromIpc(pad->location->x,
                                  pad->location->y);

    auto item = createItem(pad->feature, GftPad);
    //item->setData(ItemFactoryTypeKey, int(StatisticKey::TextRef));
    //m_statCounter[StatisticKey::PadstackHoleDef]++;
    item->setPos(location);
    item->setTransform(transform, CombinedTransfrom);

    return bindFeature(item, pad);
}

LeGraphicsItem *GraphicsItemFactory::createItem(const Ipc2581b::Feature *feature, LeGraphicsFeature featureType) const
{
    m_statCounter[StatisticKey::Feature]++;

    switch (feature->featureType())
    {
        case Ipc2581b::Feature::FeatureType::StandardShape:
            return createStandardPrimitive(feature->toStandardShape(), featureType == GftNone ? GftFill : featureType);
        case Ipc2581b::Feature::FeatureType::UserShape:
            return createUserPrimitive(feature->toUserShape(), featureType);
    }
    return nullptr;
}

/*********************************************************************
 *
 * Misc functions
 *
 ********************************************************************/

qreal GraphicsItemFactory::scaleFactor() const
{
    return m_ipcScaleFactor;
}

QString GraphicsItemFactory::unit() const
{
    return Ipc2581b::EnumTranslator::unitsText(m_ipcUnit);
}

void GraphicsItemFactory::clearDictionaries()
{
    m_brushDictionary.clear();
    m_colorDictionary.clear();
    m_penDictionary.clear();
    qDeleteAll(m_stdPrimitiveDictionary.values());
    m_stdPrimitiveDictionary.clear();
    qDeleteAll(m_userPrimitiveDictionary);
    m_userPrimitiveDictionary.clear();
}

void GraphicsItemFactory::clearStats()
{
    m_statCounter[StatisticKey::Color] = 0;
    m_statCounter[StatisticKey::ColorRef] = 0;
    m_statCounter[StatisticKey::Pen] = 0;
    m_statCounter[StatisticKey::PenRef] = 0;
    m_statCounter[StatisticKey::Brush] = 0;
    m_statCounter[StatisticKey::BrushRef] = 0;
    m_statCounter[StatisticKey::Butterfly] = 0;
    m_statCounter[StatisticKey::ButterflyRef] = 0;
    m_statCounter[StatisticKey::Circle] = 0;
    m_statCounter[StatisticKey::CircleRef] = 0;
    m_statCounter[StatisticKey::Contour] = 0;
    m_statCounter[StatisticKey::ContourRef] = 0;
    m_statCounter[StatisticKey::Diamond] = 0;
    m_statCounter[StatisticKey::DiamondRef] = 0;
    m_statCounter[StatisticKey::Donut] = 0;
    m_statCounter[StatisticKey::DonutRef] = 0;
    m_statCounter[StatisticKey::Ellipse] = 0;
    m_statCounter[StatisticKey::EllipseRef] = 0;
    m_statCounter[StatisticKey::Hexagon] = 0;
    m_statCounter[StatisticKey::HexagonRef] = 0;
    m_statCounter[StatisticKey::Moire] = 0;
    m_statCounter[StatisticKey::MoireRef] = 0;
    m_statCounter[StatisticKey::Octagon] = 0;
    m_statCounter[StatisticKey::OctagonRef] = 0;
    m_statCounter[StatisticKey::Oval] = 0;
    m_statCounter[StatisticKey::OvalRef] = 0;
    m_statCounter[StatisticKey::RectCenter] = 0;
    m_statCounter[StatisticKey::RectCenterRef] = 0;
    m_statCounter[StatisticKey::RectCham] = 0;
    m_statCounter[StatisticKey::RectChamRef] = 0;
    m_statCounter[StatisticKey::RectCorner] = 0;
    m_statCounter[StatisticKey::RectCornerRef] = 0;
    m_statCounter[StatisticKey::RectRound] = 0;
    m_statCounter[StatisticKey::RectRoundRef] = 0;
    m_statCounter[StatisticKey::Thermal] = 0;
    m_statCounter[StatisticKey::ThermalRef] = 0;
    m_statCounter[StatisticKey::Triangle] = 0;
    m_statCounter[StatisticKey::TriangleRef] = 0;
    m_statCounter[StatisticKey::StandardPrimitive] = 0;
    m_statCounter[StatisticKey::StandardPrimitiveRef] = 0;
    m_statCounter[StatisticKey::StandardShape] = 0;
    m_statCounter[StatisticKey::Arc] = 0;
    m_statCounter[StatisticKey::ArcRef] = 0;
    m_statCounter[StatisticKey::Line] = 0;
    m_statCounter[StatisticKey::LineRef] = 0;
    m_statCounter[StatisticKey::Outline] = 0;
    m_statCounter[StatisticKey::OutlineRef] = 0;
    m_statCounter[StatisticKey::Polyline] = 0;
    m_statCounter[StatisticKey::PolylineRef] = 0;
    m_statCounter[StatisticKey::Simple] = 0;
    m_statCounter[StatisticKey::SimpleRef] = 0;
    m_statCounter[StatisticKey::Text] = 0;
    m_statCounter[StatisticKey::TextRef] = 0;
    m_statCounter[StatisticKey::UserSpecial] = 0;
    m_statCounter[StatisticKey::UserSpecialRef] = 0;
    m_statCounter[StatisticKey::UserPrimitive] = 0;
    m_statCounter[StatisticKey::UserPrimitiveRef] = 0;
    m_statCounter[StatisticKey::UserShape] = 0;
    m_statCounter[StatisticKey::Feature] = 0;
    m_statCounter[StatisticKey::Features] = 0;
    m_statCounter[StatisticKey::Pad] = 0;
    m_statCounter[StatisticKey::Pin] = 0;
    m_statCounter[StatisticKey::Fiducial] = 0;
    m_statCounter[StatisticKey::Hole] = 0;
    m_statCounter[StatisticKey::Slot] = 0;
    m_statCounter[StatisticKey::Target] = 0;
    m_statCounter[StatisticKey::Marking] = 0;
    m_statCounter[StatisticKey::Transform] = 0;
}

void GraphicsItemFactory::printStats()
{
    qDebug() << "Color: " << m_statCounter[StatisticKey::Color];
    qDebug() << "ColorRef: " << m_statCounter[StatisticKey::ColorRef];
    qDebug() << "Pen: " << m_statCounter[StatisticKey::Pen];
    qDebug() << "PenRef: " << m_statCounter[StatisticKey::PenRef];
    qDebug() << "Brush: " << m_statCounter[StatisticKey::Brush];
    qDebug() << "BrushRef: " << m_statCounter[StatisticKey::BrushRef];
    qDebug() << "Butterfly: " << m_statCounter[StatisticKey::Butterfly];
    qDebug() << "ButterflyRef: " << m_statCounter[StatisticKey::ButterflyRef];
    qDebug() << "Circle: " << m_statCounter[StatisticKey::Circle];
    qDebug() << "CircleRef: " << m_statCounter[StatisticKey::CircleRef];
    qDebug() << "Contour: " << m_statCounter[StatisticKey::Contour];
    qDebug() << "ContourRef: " << m_statCounter[StatisticKey::ContourRef];
    qDebug() << "Diamond: " << m_statCounter[StatisticKey::Diamond];
    qDebug() << "DiamondRef: " << m_statCounter[StatisticKey::DiamondRef];
    qDebug() << "Donut: " << m_statCounter[StatisticKey::Donut];
    qDebug() << "DonutRef: " << m_statCounter[StatisticKey::DonutRef];
    qDebug() << "Ellipse: " << m_statCounter[StatisticKey::Ellipse];
    qDebug() << "EllipseRef: " << m_statCounter[StatisticKey::EllipseRef];
    qDebug() << "Hexagon: " << m_statCounter[StatisticKey::Hexagon];
    qDebug() << "HexagonRef: " << m_statCounter[StatisticKey::HexagonRef];
    qDebug() << "Moire: " << m_statCounter[StatisticKey::Moire];
    qDebug() << "MoireRef: " << m_statCounter[StatisticKey::MoireRef];
    qDebug() << "Octagon: " << m_statCounter[StatisticKey::Octagon];
    qDebug() << "OctagonRef: " << m_statCounter[StatisticKey::OctagonRef];
    qDebug() << "Oval: " << m_statCounter[StatisticKey::Oval];
    qDebug() << "OvalRef: " << m_statCounter[StatisticKey::OvalRef];
    qDebug() << "RectCenter: " << m_statCounter[StatisticKey::RectCenter];
    qDebug() << "RectCenterRef: " << m_statCounter[StatisticKey::RectCenterRef];
    qDebug() << "RectCham: " << m_statCounter[StatisticKey::RectCham];
    qDebug() << "RectChamRef: " << m_statCounter[StatisticKey::RectChamRef];
    qDebug() << "RectCorner: " << m_statCounter[StatisticKey::RectCorner];
    qDebug() << "RectCornerRef: " << m_statCounter[StatisticKey::RectCornerRef];
    qDebug() << "RectRound: " << m_statCounter[StatisticKey::RectRound];
    qDebug() << "RectRoundRef: " << m_statCounter[StatisticKey::RectRoundRef];
    qDebug() << "Thermal: " << m_statCounter[StatisticKey::Thermal];
    qDebug() << "ThermalRef: " << m_statCounter[StatisticKey::ThermalRef];
    qDebug() << "Triangle: " << m_statCounter[StatisticKey::Triangle];
    qDebug() << "TriangleRef: " << m_statCounter[StatisticKey::TriangleRef];
    qDebug() << "StandardPrimitive: " << m_statCounter[StatisticKey::StandardPrimitive];
    qDebug() << "StandardPrimitiveRef: " << m_statCounter[StatisticKey::StandardPrimitiveRef];
    qDebug() << "StandardShape: " << m_statCounter[StatisticKey::StandardShape];
    qDebug() << "Arc: " << m_statCounter[StatisticKey::Arc];
    qDebug() << "ArcRef: " << m_statCounter[StatisticKey::ArcRef];
    qDebug() << "Line: " << m_statCounter[StatisticKey::Line];
    qDebug() << "LineRef: " << m_statCounter[StatisticKey::LineRef];
    qDebug() << "Outline: " << m_statCounter[StatisticKey::Outline];
    qDebug() << "OutlineRef: " << m_statCounter[StatisticKey::OutlineRef];
    qDebug() << "Polyline: " << m_statCounter[StatisticKey::Polyline];
    qDebug() << "PolylineRef: " << m_statCounter[StatisticKey::PolylineRef];
    qDebug() << "Simple: " << m_statCounter[StatisticKey::Simple];
    qDebug() << "SimpleRef: " << m_statCounter[StatisticKey::SimpleRef];
    qDebug() << "Text: " << m_statCounter[StatisticKey::Text];
    qDebug() << "TextRef: " << m_statCounter[StatisticKey::TextRef];
    qDebug() << "UserSpecial: " << m_statCounter[StatisticKey::UserSpecial];
    qDebug() << "UserSpecialRef: " << m_statCounter[StatisticKey::UserSpecialRef];
    qDebug() << "UserPrimitive: " << m_statCounter[StatisticKey::UserPrimitive];
    qDebug() << "UserPrimitiveRef: " << m_statCounter[StatisticKey::UserPrimitiveRef];
    qDebug() << "UserShape: " << m_statCounter[StatisticKey::UserShape];
    qDebug() << "Feature: " << m_statCounter[StatisticKey::Feature];
    qDebug() << "Features: " << m_statCounter[StatisticKey::Features];
    qDebug() << "Pad: " << m_statCounter[StatisticKey::Pad];
    qDebug() << "Pin: " << m_statCounter[StatisticKey::Pin];
    qDebug() << "Fiducial: " << m_statCounter[StatisticKey::Fiducial];
    qDebug() << "Hole: " << m_statCounter[StatisticKey::Hole];
    qDebug() << "Slot: " << m_statCounter[StatisticKey::Slot];
    qDebug() << "Target: " << m_statCounter[StatisticKey::Target];
    qDebug() << "Marking: " << m_statCounter[StatisticKey::Marking];
    qDebug() << "Transform: " << m_statCounter[StatisticKey::Transform];
}
